﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

/// <summary>
/// Converts dungeon data into tile index data.
/// </summary>
public class DungeonIndexer
{
    /// <summary>
    /// The dungeon being indexed.
    /// </summary>
    private Dungeon dungeon = null;

    /// <summary>
    /// A collection of tiles for each tile in the dungeon that actually exists.
    /// The key is the integer index of the tile's position if the dungeon were laid out
    /// as a single-indexed array. The value is a descriptor that can be used to get a prefab.
    /// </summary>
    public Dictionary<int, TileDescriptor> TileDescriptors { get; private set; }

    /// <summary>
    /// A collection of horizontal walls for the dungeon.
    /// The key is the integer index of an open tile. 
    /// The value is whether or not there is a north wall on that space.
    /// </summary>
    private Dictionary<int, bool> horizontalWalls = null;

    /// <summary>
    /// A collection of vertical walls for the dungeon.
    /// The key is the integer index of an open tile.
    /// The value is whether or not there is a west wall on that space.
    /// </summary>
    private Dictionary<int, bool> verticalWalls = null;

    /// <summary>
    /// A list of open spaces. The values stored in the list are integer indexes of tile spaces in the dungeon.
    /// </summary>
    private Dictionary<int, int> openSpaces = null;

    public static DungeonIndexer GetEmptyIndexer(Dungeon dungeon)
    {
        DungeonIndexer emptyIndexer = new DungeonIndexer();
        emptyIndexer.dungeon = dungeon;
        emptyIndexer.TileDescriptors = new Dictionary<int, TileDescriptor>();
        return emptyIndexer;
    }

    private DungeonIndexer()
    {
    }

    public DungeonIndexer(Dungeon dungeon)
    {
        TileDescriptors = new Dictionary<int, TileDescriptor>();
        this.dungeon = dungeon;
        IndexDungeon();

        StringBuilder dataBuilder = new StringBuilder();
        for (int y = 0; y < dungeon.Size; y++) 
        {
            for (int x = 0; x < dungeon.Size; x++) 
            {
                if (TileDescriptors.Keys.Contains(TileC(x, y)))
                    dataBuilder.AppendFormat("{0}\t", GetTileIndex(x, y));
                else
                    dataBuilder.AppendFormat("\t");
            }
            dataBuilder.AppendLine();
        }

#if UNITY_WEB_PLAYER
#else

        UnityEngine.MonoBehaviour.print(System.Environment.CurrentDirectory + "\\tileIndexes.txt");
        System.IO.File.WriteAllText(System.Environment.CurrentDirectory + "\\tileIndexes.txt", dataBuilder.ToString());
#endif
        UnityEngine.MonoBehaviour.print(dataBuilder.ToString());

    }

    private void IndexDungeon()
    {
        DateTime startDateTime = DateTime.Now;
        GetOpenSpaces();
        DateTime openSpacesExecuted = DateTime.Now;
        GetHorizontalWalls();
        DateTime horizontalWallsFound = DateTime.Now;
        GetVerticalWalls();
        DateTime verticalWallsFound = DateTime.Now;
        PopulateDescriptors();
        DateTime descriptorsPopulated = DateTime.Now;

        UnityEngine.MonoBehaviour.print("open spaces in " + (openSpacesExecuted - startDateTime).Seconds);
        UnityEngine.MonoBehaviour.print("horizontal walls in " + (horizontalWallsFound - openSpacesExecuted).Seconds);
        UnityEngine.MonoBehaviour.print("vertical walls in " + (verticalWallsFound - horizontalWallsFound).Seconds);
        UnityEngine.MonoBehaviour.print("descriptors done in " + (descriptorsPopulated - verticalWallsFound).Seconds);
    }

    private void GetOpenSpaces()
    {
        openSpaces = new Dictionary<int, int>();

        // 1. Open rooms
        foreach (Room room in dungeon.Rooms)
            for (int roomY = room.Y; roomY < room.Y + room.Height; roomY++)
                for (int roomX = room.X; roomX < room.X + room.Width; roomX++)
                    openSpaces.Add(TileC(roomX, roomY), 0);

        // 2. Open corridors
        foreach (Corridor corridor in dungeon.Corridors)
            for (int corridorY = corridor.MinY; corridorY <= corridor.MaxY; corridorY++)
                for (int corridorX = corridor.MinX; corridorX <= corridor.MaxX; corridorX++)
                    if (!openSpaces.ContainsKey(TileC(corridorX, corridorY)))
                        openSpaces.Add(TileC(corridorX, corridorY), 0);
    }

    private void GetVerticalWalls()
    {
        // Initialize the collection.
        verticalWalls = new Dictionary<int, bool>();

        // Iterate each open space.
        foreach (int openSpaceIndex in openSpaces.Keys)
        {
            // If we're all the way to the left, we always have a wall.
            // Otherwise, we only have a wall if the space to the left isn't open..

            if (openSpaceIndex % dungeon.Size == 0)
                verticalWalls.Add(openSpaceIndex, true);
            else
                verticalWalls.Add(openSpaceIndex, !openSpaces.ContainsKey(openSpaceIndex - 1));
        }
    }

    private void GetHorizontalWalls()
    {
        // Initialize the collection.
        horizontalWalls = new Dictionary<int,bool>();

        // Iterate each open space.
        foreach (int openSpaceIndex in openSpaces.Keys)
        {
            // If we're on the top row, we always have a north wall.
            // Otherwise, check to see if the room above us is empty; add a wall if it isn't.

            if (openSpaceIndex < dungeon.Size)
                horizontalWalls.Add(openSpaceIndex, true);
            else
                horizontalWalls.Add(openSpaceIndex, !openSpaces.ContainsKey(openSpaceIndex - dungeon.Size));
        }

    }

    private void PopulateDescriptors()
    {
        TileDescriptors = new Dictionary<int, TileDescriptor>();
        foreach (int openSpaceIndex in openSpaces.Keys)
            TileDescriptors.Add(openSpaceIndex, GetTileDescriptor(openSpaceIndex % dungeon.Size, openSpaceIndex / dungeon.Size));

    }

    private TileDescriptor GetTileDescriptor(int x, int y)
    {
        bool southWall = GetSouthWall(x, y);
        bool westWall = GetWestWall(x, y);
        bool northWall = GetNorthWall(x, y);
        bool eastWall = GetEastWall(x, y);

        bool southwestCorner = !southWall && !westWall && GetSouthWall(x - 1, y) && GetWestWall(x, y + 1);
        bool northwestCorner = !northWall && !westWall && GetNorthWall(x - 1, y) && GetWestWall(x, y - 1);
        bool northeastCorner = !northWall && !eastWall && GetNorthWall(x + 1, y) && GetEastWall(x, y - 1);
        bool southeastCorner = !southWall && !eastWall && GetSouthWall(x + 1, y) && GetEastWall(x, y + 1);

        return new TileDescriptor(
            (southWall ? 128 : 0) +
            (westWall ? 64 : 0) +
            (northWall ? 32 : 0) +
            (eastWall ? 16 : 0) +
            (southwestCorner ? 8 : 0) +
            (northwestCorner ? 4 : 0) +
            (northeastCorner ? 2 : 0) +
            (southeastCorner ? 1 : 0));
    }

    private bool GetNorthWall(int x, int y)
    {
        if (y <= 0) return true;
        if (!openSpaces.ContainsKey(TileC(x, y))) return true;
        return horizontalWalls[TileC(x, y)];
    }

    private bool GetSouthWall(int x, int y)
    {
        if (y >= dungeon.Size - 1) return true;
        if (!openSpaces.ContainsKey(TileC(x, y + 1))) return true;
        return horizontalWalls[TileC(x, y + 1)];
    }

    private bool GetWestWall(int x, int y)
    {
        if (x <= 0) return true;
        if (!openSpaces.ContainsKey(TileC(x, y))) return true;
        return verticalWalls[TileC(x, y)];
    }

    private bool GetEastWall(int x, int y)
    {
        if (x >= dungeon.Size - 1) return true;
        if (!openSpaces.ContainsKey(TileC(x + 1, y))) return true;
        return verticalWalls[TileC(x + 1, y)];
    }

    public int GetTileIndex(int x, int y)
    {
        int locationIndex = TileC(x, y);
        if (TileDescriptors.ContainsKey(locationIndex)) return TileDescriptors[locationIndex].ToInt();
        return -1;
    }

    public int TileC(int x, int y)
    {
        return dungeon.Size * y + x;
    }

}
